package tzhomestudio.framework.implementation.config_service;

import tzhomestudio.framework.implementation.main_service.FrameworkMainServiceHelper;
import tzhomestudio.framework.interfaces.config_service.IConfig;
import tzhomestudio.framework.shared.utils.FileUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

/**
 * Singleton class for keeping decoders (or plugins) properties.
 *
 * @author Timur Zolotuhin (e-mail: tzolotuhin@gmail.com)
 */
public class FileConfig implements IConfig {
    // Constants
    private static final String CONFIG_FILE_NAME = "config.ini";

    // Main data
    private Properties prop;
    private File configFile;

    private boolean saveWithExit = true;

    public FileConfig() {
        this(new File(FileUtils.getWorkingDir() + CONFIG_FILE_NAME));
    }

    public FileConfig(File configFile) {
        this.configFile = configFile;

        // create log directory, if it isn't exist
        File configDir = configFile.getParentFile();
        if(!configDir.exists() && !configDir.mkdirs()) {
            throw new RuntimeException("Couldn't to create conf directory " + configDir.getAbsolutePath());
        }

        // read properties
        FileInputStream in = null;
		try {
			in = new FileInputStream(configFile);
			this.prop = new Properties();
			this.prop.load(in);
		} catch (IOException ex) {
			this.prop = new Properties();
            FrameworkMainServiceHelper.logger.printError("Couldn't open file " + configFile + ".");
            FrameworkMainServiceHelper.logger.printError("Try to create new config file.");
			try {
				if(configFile.createNewFile()) {
                    FrameworkMainServiceHelper.logger.printError("Creating of config file. Successfully");
				} else {
                    FrameworkMainServiceHelper.logger.printError("Creating of config file. Fail.");
				}
			} catch (IOException e) {
                FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
			}
		} finally {
			if(in != null) {
				try {
					in.close();
				} catch (IOException ex) {
                    FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
                }
			}
		}
		
		//finalize of services
		Runtime.getRuntime().addShutdownHook(new Thread() {
			public void run() {
				save();
			}
		});	
	}

    @Override
    public synchronized String getProperty(String key) {
        String value = null;
        
        if (this.prop.containsKey(key))
            value = (String)this.prop.get(key);
        return value;
    }

    @Override
    public synchronized String getStringProperty(String key) {
        String value = getProperty(key);

        if (value == null) {
            throw new NullPointerException("Element with key" + key + " was not found");
        }

        return value;
    }

    @Override
    public String getStringProperty(String key, String defaultValue) {
        try {
            return getStringProperty(key);
        } catch (Throwable ex) {
            FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
            setProperty(key, defaultValue);
        }

        return defaultValue;
    }

    @Override
    public synchronized boolean getBooleanProperty(String key) {
        String value = getProperty(key);

        if (value != null) {
            return Boolean.valueOf(value);
        } else {
            throw new NullPointerException("Element with key" + key + " was not found");
        }
    }

    @Override
    public synchronized boolean getBooleanProperty(String key, boolean defaultValue) {
        try {
            return getBooleanProperty(key);
        } catch (Throwable ex) {
            FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
            setProperty(key, Boolean.toString(defaultValue));
        }

        return defaultValue;
    }

    @Override
    public synchronized int getIntegerProperty(String key) {
        return getIntegerProperty(key, 0);
    }

    @Override
    public synchronized int getIntegerProperty(String key, int defaultValue) {
        String value = getProperty(key);

        if (value != null) {
            try {
                return Integer.valueOf(value);
            } catch (Throwable ex) {
                FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
            }
        }

        return defaultValue;
    }

    @Override
    public synchronized float getFloatProperty(String key) {
        String value = getProperty(key);

        if (value != null) {
            try {
                return Float.valueOf(value);
            } catch (Throwable ex) {
                FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
            }
        }

        return 0;
    }

    @Override
    public synchronized void setProperty(String key, String value) {
		if (value == null)
			this.prop.remove(key);
		else
			this.prop.setProperty(key, value);
	}

    @Override
    public synchronized void save() {
        OutputStreamWriter out = null;
        try {
			out = new OutputStreamWriter(new FileOutputStream(configFile), "UTF-8");

            List<String> keys = new ArrayList<>();
            for (Object key : prop.keySet()) {
                if (key != null)
                    keys.add(key.toString());
            }

            String[] arrayKeys = new String[keys.size()];
            keys.toArray(arrayKeys);
            Arrays.sort(arrayKeys);

            String lineSeparator = System.getProperty("line.separator");

            for (String key : arrayKeys) {
                Object value = prop.get(key);

                if (value != null) {
                    String outStr = (key + "=" + value.toString()).replace("\\", "\\\\");

                    out.write(outStr + lineSeparator);
                }
            }
		} catch (Throwable ex) {
			System.err.println(ex.getMessage());
		} finally {
			if(out != null) {
				try {
					out.close();
				} catch(IOException ex) {
                    FrameworkMainServiceHelper.logger.printDebug("Please, ignore this exception", ex);
                }
			}
		}
	}

    @Override
    public synchronized void saveWithExit(boolean state) {
        saveWithExit = state;
    }

    @Override
    public synchronized boolean isSaveWithExit() {
        return saveWithExit;
    }
}
